package com.core.sdk.core;

import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import android.app.Application;
import android.content.Context;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;

import com.core.sdk.receiver.NetWorkReceiver;
import com.core.sdk.utils.DateUtil;
import com.core.sdk.utils.ExceptionUtil;
import com.core.sdk.utils.IOUtil;
import com.core.sdk.utils.ToastUtil;

public abstract class BaseApplication extends Application implements UncaughtExceptionHandler {
	protected final String TAG = this.getClass().getSimpleName();
	private static volatile ConcurrentHashMap<String, MessageHandListener> ttListenerMap = new ConcurrentHashMap<String, MessageHandListener>();
	/** UI线程ID */
	private static volatile long uiTid = -1;

	private static final Handler handler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			if (uiTid <= 0) {
				uiTid = Thread.currentThread().getId();
				Thread.currentThread().setName("T1-UI");
			}
			if (msg == null || msg.obj == null || (msg.obj instanceof BaseMessage) == false)
				return;
			BaseMessage ttMsg = (BaseMessage) msg.obj;
			String from = ttMsg.getFrom().getUri();
			MessageHandListener ttMsgListener = ttListenerMap.get(from);
			if (ttMsgListener != null)
				ttMsgListener.executeMessage(ttMsg.getMsg());
		}
	};

	@Override
	public void onCreate() {
		super.onCreate();
		defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
		Thread.setDefaultUncaughtExceptionHandler(this);
		init();
	}

	private void init() {
		registReceivers();
	}

	@Override
	public void onTerminate() {
		super.onTerminate();
		this.unregisterReceiver(mNetWorkReceiver);
	}

	void sendMessage(BaseMessage tmsg) {
		Message msg = handler.obtainMessage();
		msg.obj = tmsg;
		handler.sendMessage(msg);
	}

	void sendMessageDelayed(BaseMessage tmsg, long delayMillis) {
		Message msg = handler.obtainMessage();
		msg.obj = tmsg;
		handler.sendMessageDelayed(msg, delayMillis);
	}
	void removeMessage(int what){
		handler.removeMessages(what);
	}

	void sendEvent(BaseEvent evt) throws BaseException {
		if (evt == null)
			throw new BaseException("evt is null");
		if (evt.getFrom() == null)
			throw new BaseException("evt.from is null");
		if (evt.getTo() == null)
			throw new BaseException("evt.to is null");
		String to = evt.getTo().getUri().trim();
		executors.execute(new EventWorker(evt));
		if (ttListenerMap.containsKey(to) || Location.any.getUri().equals(to)) {
		} else {
			LogUtil.e(TAG, "to:" + to + " can't register");
		}
	}

	void registerTtListener(MessageHandListener tl) {
		ttListenerMap.put(tl.getClass().getName(), tl);
	}

	void unRegisterTtListener(MessageHandListener tl) {
		ttListenerMap.remove(tl.getClass().getName());
	}

	private class EventWorker implements Runnable {
		private BaseEvent evt;

		public EventWorker(BaseEvent evt) {
			super();
			this.evt = evt;
		}

		@Override
		public void run() {
			String to = evt.getTo().getUri();
			if (to == null || to.trim().length() == 0)
				return;
			if (Location.any.getUri().equals(to)) {
				for (MessageHandListener lt : ttListenerMap.values()) {
					lt.executeEvent(evt);
				}
				return;
			}
			MessageHandListener lt = ttListenerMap.get(to);
			if (lt == null)
				return;
			lt.executeEvent(evt);
		}
	}

	void checkRunOnUI() {
		if (Thread.currentThread().getId() != uiTid)
			throw new IllegalStateException("not run on UI Thread");
	}

	void checkRunOnMain() {
		if (!Thread.currentThread().getName().startsWith(T2_THREAD_NAME))
			throw new IllegalStateException("not run on Main Thread");
	}

	void postRunOnUi(UITask task) {
		handler.post(task);
	}

	// -------------------------------------------------------
	NetWorkReceiver mNetWorkReceiver = null;

	private void registReceivers() {
		initNetWorkReceiver();
		IntentFilter mNetWrokFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
		this.registerReceiver(mNetWorkReceiver, mNetWrokFilter);
	}

	protected void initNetWorkReceiver() {
		mNetWorkReceiver = new NetWorkReceiver();
	}

	// ====================================================
	private static final String T2_THREAD_NAME="T2-MainThread#";
	/** 持久化线程工厂 */
	private static final ThreadFactory threadFactory = new ThreadFactory() {
		private final AtomicLong mCount = new AtomicLong(1);

		@Override
		public Thread newThread(Runnable r) {
			return new Thread(r, T2_THREAD_NAME + mCount.getAndIncrement());
		}
	};
	private static final int CORE_POOL_SIZE = 5;
	private static final int MAXIMUM_POOL_SIZE = 128;
	private static final int KEEP_ALIVE = 10;
	private final ThreadPoolExecutor executors = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10), threadFactory);

	// ==========================全局异常处理===========================

	private UncaughtExceptionHandler defaultHandler = null;

	@Override
	public void uncaughtException(Thread thread, Throwable ex) {
		LogUtil.e(TAG, "App crash:", ex);
		if (handleException(ex)) {
			LooperThread lt = new LooperThread();
			lt.start();			
		} else if (defaultHandler != null) {
			defaultHandler.uncaughtException(thread, ex);
		} else {
			LogUtil.e(TAG, "unhandled exception", ex);
			exit();
		}
	}

	private void exit() {
		android.os.Process.killProcess(android.os.Process.myPid());
		//System.exit(1);
	}

	/** 获得设备信息并写入到文件 */
	private boolean handleException(Throwable ex) {
		collectDeviceInfo(getApplicationContext());
		saveCrashInfoToFile(ex);
		//TODO 上传到网络，同时需要注意等传输完成再关闭进程
		return true;
	}

	// 用来存储设备信息和异常信息
	private Map<String, String> infos = new HashMap<String, String>();

	/** 收集设备信息  */
	private void collectDeviceInfo(Context ctx) {
		try {
			PackageManager pm = ctx.getPackageManager();
			PackageInfo pi = pm.getPackageInfo(ctx.getPackageName(), PackageManager.GET_ACTIVITIES);
			if (pi != null) {
				String versionName = pi.versionName == null ? "null" : pi.versionName;
				String versionCode = pi.versionCode + "";
				infos.put("versionName", versionName);
				infos.put("versionCode", versionCode);
			}
		} catch (Exception e) {
			LogUtil.e(TAG, "an error occured when collect package info", e);
		}

		Field[] fields = Build.class.getDeclaredFields();
		for (Field field : fields) {
			try {
				field.setAccessible(true);
				infos.put(field.getName(), field.get(null).toString());
				LogUtil.d(TAG, field.getName() + " : " + field.get(null));
			} catch (Exception e) {
				LogUtil.e(TAG, "an error occured when collect crash info", e);
			}
		}
	}

	/** 保存错误信息到文件中 */
	private String saveCrashInfoToFile(Throwable ex) {
		StringBuffer sb = new StringBuffer();
		for (Map.Entry<String, String> entry : infos.entrySet()) {
			String key = entry.getKey();
			String value = entry.getValue();
			sb.append(key + "=" + value + "\n");
		}
		String result = ExceptionUtil.getStackTrace(ex);
		sb.append(result);
		try {
			String dir = IOUtil.getExternalStoragePath()+"aaa_dir/";
			IOUtil.mkDir(dir);
			String time = DateUtil.formatDate(System.currentTimeMillis(), "yyyyMMddHHmmss");
			String fileName = time + ".log";
			String fileFullPath =  dir+ fileName;
			IOUtil.saveFile(fileFullPath, sb.toString().getBytes());
			return fileFullPath;
		} catch (Exception e) {
			Log.e(TAG, "an error occured while writing file...", e);
		}
		return null;
	}
	
    private class LooperThread extends Thread {  
        public Handler mHandler;
		public void run() {  
            Looper.prepare();  
            mHandler = new Handler() {  
                public void handleMessage(Message msg) {  
                	if(msg.what ==1){
                		ToastUtil.show(getApplicationContext(), "抱歉！程序出错,即将退出");
                		this.sendEmptyMessageDelayed(2, 2000);
                	}else if(msg.what == 2){
                		exit();
                	}
                }  
            };  
            mHandler.sendEmptyMessage(1);
            Looper.loop();  
        }
    }

}
